/*
*  Copyright 2019-2020 Zheng Jie
*
*  Licensed under the Apache License, Version 2.0 (the "License");
*  you may not use this file except in compliance with the License.
*  You may obtain a copy of the License at
*
*  http://www.apache.org/licenses/LICENSE-2.0
*
*  Unless required by applicable law or agreed to in writing, software
*  distributed under the License is distributed on an "AS IS" BASIS,
*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*  See the License for the specific language governing permissions and
*  limitations under the License.
*/
package me.zhengjie.modules.anniversary.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import lombok.RequiredArgsConstructor;
import me.zhengjie.exception.BadRequestException;
import me.zhengjie.modules.anniversary.domain.Department;
import me.zhengjie.modules.anniversary.repository.DepartmentRepository;
import me.zhengjie.modules.anniversary.service.DepartmentService;
import me.zhengjie.modules.anniversary.service.dto.DepartmentDto;
import me.zhengjie.modules.anniversary.service.dto.DepartmentQueryCriteria;
import me.zhengjie.modules.anniversary.service.dto.MajorSmallDto;
import me.zhengjie.modules.anniversary.service.mapstruct.DepartmentMapper;
import me.zhengjie.modules.anniversary.service.mapstruct.MajorSmallMapper;
import me.zhengjie.utils.FileUtil;
import me.zhengjie.utils.QueryHelp;
import me.zhengjie.utils.RedisUtils;
import me.zhengjie.utils.ValidationUtil;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Field;
import java.sql.Timestamp;
import java.util.*;
import java.util.stream.Collectors;

/**
* @website https://el-admin.vip
* @description 服务实现
* @author Jiayin Wei
* @date 2020-07-22
**/
@Service
@RequiredArgsConstructor
@CacheConfig(cacheNames = "department")
public class DepartmentServiceImpl implements DepartmentService {

    private final DepartmentRepository departmentRepository;
    private final DepartmentMapper departmentMapper;
    private final MajorSmallMapper majorSmallMapper;
    private final RedisUtils redisUtils;

    @Override
    public List<DepartmentDto> queryAll(DepartmentQueryCriteria criteria, Boolean isQuery) throws Exception{
        Sort sort = new Sort(Sort.Direction.ASC, "sort");
        if (isQuery) {
            criteria.setParentIdIsNull(true);
            List<Field> fields = QueryHelp.getAllFields(criteria.getClass(), new ArrayList<>());
            List<String> fieldNames = new ArrayList<String>(){{ add("parentIdIsNull");add("year");add("isEnable");}};
            for (Field field : fields) {
                //设置对象的访问权限，保证对private的属性的访问
                field.setAccessible(true);
                Object val = field.get(criteria);
                if(fieldNames.contains(field.getName())){
                    continue;
                }
                if (ObjectUtil.isNotNull(val)) {
                    criteria.setParentIdIsNull(null);
                    break;
                }
            }
        }

        return departmentMapper.toDto(departmentRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),sort));
    }

    @Override
    @Cacheable(key = "'id:' + #p0")
    public DepartmentDto findById(Long id) {
        Department department = departmentRepository.findById(id).orElseGet(Department::new);
        ValidationUtil.isNull(department.getId(),"Department","id",id);
        return departmentMapper.toDto(department);
    }

    @Override
    @Cacheable(key = "'parentId:' + #p0")
    public List<Department> findByParentId(long parentId) {
        return departmentRepository.findByParentId(parentId);
    }

    @Override
    public List<Integer> getDepartmentYears()
    {
        return departmentRepository.getDepartmentYears();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void create(Department resources) {
        departmentRepository.save(resources);
        // 计算子节点数目
        resources.setSubCount(0);
        // 清理缓存
        redisUtils.del("department::parentid:" + (resources.getParentId() == null ? 0 : resources.getParentId()));
        updateSubCnt(resources.getParentId());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void update(Department resources) {
        // 旧的部门
        Long oldPid = findById(resources.getId()).getParentId();
        Long newPid = resources.getParentId();
        if(resources.getParentId() != null && resources.getId().equals(resources.getParentId())) {
            throw new BadRequestException("上级不能为自己");
        }
        Department department = departmentRepository.findById(resources.getId()).orElseGet(Department::new);
        ValidationUtil.isNull( department.getId(),"Department","id",resources.getId());
        resources.setId(department.getId());
        departmentRepository.save(resources);
        // 更新父节点中子节点数目
        updateSubCnt(oldPid);
        updateSubCnt(newPid);
        // 清理缓存
        delCaches(resources.getId(), oldPid, newPid);

        department.copy(resources);
        departmentRepository.save(department);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(Set<DepartmentDto> departmentDtos) {
        for (DepartmentDto departmentDto : departmentDtos) {
            // 清理缓存
            delCaches(departmentDto.getId(), departmentDto.getParentId(), null);
            departmentRepository.deleteById(departmentDto.getId());
            updateSubCnt(departmentDto.getParentId());
        }
    }

    @Override
    public void download(List<DepartmentDto> all, HttpServletResponse response) throws IOException {
        List<Map<String, Object>> list = new ArrayList<>();
        for (DepartmentDto department : all) {
            Map<String,Object> map = new LinkedHashMap<>();
            map.put("名称", department.getName());
            map.put("年份", department.getYear());
            map.put("成立日期", department.getEstabDate());
            map.put("类型", department.getType());
            map.put("子部门数", department.getSubCount());
            map.put("上级部门ID", department.getParentId());
            map.put("已认证人数", department.getCertifiedNum());
            map.put("创建者名称", department.getCreateBy());
            map.put("创建时间", department.getCreateTime());
            map.put("修改者名称", department.getUpdatedBy());
            map.put("修改时间", department.getUpdateTime());
            map.put("排序", department.getSort());
            map.put("备注", department.getRemark());
            map.put("状态", department.getIsEnable());
            list.add(map);
        }
        FileUtil.downloadExcel(list, response);
    }

    @Override
    public Set<DepartmentDto> getDeleteDepts(List<Department> departmentList, Set<DepartmentDto> departmentDtos) {
        for (Department dept : departmentList) {
            departmentDtos.add(departmentMapper.toDto(dept));
            List<Department> depts = departmentRepository.findByParentId(dept.getId());
            if(depts!=null && depts.size()!=0){
                getDeleteDepts(depts, departmentDtos);
            }
        }
        return departmentDtos;
    }

    @Override
    public List<Long> getDeptChildren(Long deptId, List<Department> deptList) {
        List<Long> list = new ArrayList<>();
        deptList.forEach(dept -> {
                    if (dept!=null && dept.getIsEnable()){
                        List<Department> depts = departmentRepository.findByParentId(dept.getId());
                        if(deptList.size() != 0){
                            list.addAll(getDeptChildren(dept.getId(), depts));
                        }
                        list.add(dept.getId());
                    }
                }
        );
        return list;
    }

    @Override
    public List<DepartmentDto> getSuperior(DepartmentDto deptDto, List<Department> departments) {
        if(deptDto.getParentId() == null){
            departments.addAll(departmentRepository.findByParentIdIsNull());
            return departmentMapper.toDto(departments);
        }
        departments.addAll(departmentRepository.findByParentId(deptDto.getParentId()));
        return getSuperior(findById(deptDto.getParentId()), departments);
    }

    @Override
    public List<MajorSmallDto> getMajors(Integer year){
        return majorSmallMapper.toDto(departmentRepository.findMajorsByYear(year));
    }

    @Override
    public Object buildTree(List<DepartmentDto> deptDtos) {
        Set<DepartmentDto> trees = new LinkedHashSet<>();
        Set<DepartmentDto> depts= new LinkedHashSet<>();
        List<String> deptNames = deptDtos.stream().map(DepartmentDto::getName).collect(Collectors.toList());
        boolean isChild;
        for (DepartmentDto deptDTO : deptDtos) {
            isChild = false;
            if (deptDTO.getParentId() == null) {
                boolean exists = false;
                for (DepartmentDto item: trees ) {
                    if (item.getName().equals(deptDTO.getYear().toString() + "年")){
                        exists = true;
                        item.getChildren().add(deptDTO);
                        item.setSubCount(item.getSubCount() + 1);
                    }
                }
                if(exists == false){
                    DepartmentDto deptTmp = new DepartmentDto();
                    deptTmp.setId(deptDTO.getYear().longValue() * -1);
                    deptTmp.setName(deptDTO.getYear().toString() + "年");
                    deptTmp.setYear(deptDTO.getYear());
                    deptTmp.setEstabDate(new Timestamp(System.currentTimeMillis()));
                    deptTmp.setType(-1);
                    deptTmp.setParentId(0L);
                    deptTmp.setSubCount(1);
                    deptTmp.setCertifiedNum(deptDTO.getCertifiedNum());
                    deptTmp.setSort(0);
                    deptTmp.setRemark("");
                    deptTmp.setIsEnable(true);
                    deptTmp.setChildren(new ArrayList<>());
                    deptTmp.getChildren().add(deptDTO);
                    trees.add(deptTmp);
                }
            }
            for (DepartmentDto it : deptDtos) {
                if (it.getParentId() != null && deptDTO.getId().equals(it.getParentId())) {
                    isChild = true;
                    if (deptDTO.getChildren() == null) {
                        deptDTO.setChildren(new ArrayList<>());
                    }
                    deptDTO.getChildren().add(it);
                }
            }
            if(isChild) {
                depts.add(deptDTO);
            } else if(deptDTO.getParentId() != null &&  !deptNames.contains(findById(deptDTO.getParentId()).getName())) {
                depts.add(deptDTO);
            }
        }

        if (CollectionUtil.isEmpty(trees)) {
            trees = depts;
        }
        Map<String,Object> map = new HashMap<>(2);
        map.put("totalElements",deptDtos.size());
        map.put("content",CollectionUtil.isEmpty(trees)? deptDtos :trees);
        return map;
    }



    private void updateSubCnt(Long deptId){
        if(deptId != null){
            int count = departmentRepository.countByParentId(deptId);
            departmentRepository.updateSubCntById(count, deptId);
        }
    }

    /**
     * 清理缓存
     * @param id /
     * @param oldPid /
     * @param newPid /
     */
    public void delCaches(Long id, Long oldPid, Long newPid){
        // 删除数据权限
        redisUtils.del("department::id:" + id);
        redisUtils.del("department::parentid:" + (oldPid == null ? 0 : oldPid));
        redisUtils.del("department::parentid:" + (newPid == null ? 0 : newPid));
    }
}